package com.study.crypto.signer.pdf.signer;

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.signatures.BouncyCastleDigest;
import com.itextpdf.signatures.PdfPKCS7;
import com.itextpdf.signatures.PdfSigner;
import com.itextpdf.signatures.PrivateKeySignature;
import com.study.crypto.signer.pdf.container.ReadySignatureContainer;
import org.apache.http.util.Asserts;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

/**
 * 使用外传摘要方式，进行 pdf 签名
 * @author Songjin
 * @since 2022-05-06 13:08
 */
public abstract class PdfPKCS7DeferredSigner {
    
    protected final PrivateKey privateKey;
    protected final Certificate certificate;
    protected final byte[] digest;
    protected String digestAlgo;
    
    protected PdfPKCS7DeferredSigner(PrivateKey privateKey, Certificate certificate, byte[] digest) {
        this.privateKey = privateKey;
        this.certificate = certificate;
        this.digest = digest;
    }
    
    /**
     * 分步签名
     * @param sigFieldName 签名域名称
     * @param toSignedBytes 待签名数据
     * @return byte[]
     * @throws GeneralSecurityException 异常
     * @throws IOException 异常
     */
    public byte[] signDeferred(String sigFieldName, byte[] toSignedBytes) throws GeneralSecurityException, IOException {
        Asserts.notBlank(digestAlgo, "需要设定摘要算法名称");
        
        String provider = BouncyCastleProvider.PROVIDER_NAME;
        CertificateFactory instance = CertificateFactory.getInstance("X.509", provider);
        ByteArrayInputStream certInput = new ByteArrayInputStream(certificate.getEncoded());
        X509Certificate chain0 = (X509Certificate) instance.generateCertificate(certInput);
        X509Certificate[] chain = new X509Certificate[]{chain0};
        PdfPKCS7 pkcs7 = new PdfPKCS7(null, chain, digestAlgo, null, new BouncyCastleDigest(), false);
        byte[] attributes = pkcs7.getAuthenticatedAttributeBytes(digest, PdfSigner.CryptoStandard.CMS, null, null);
        PrivateKeySignature signature = new PrivateKeySignature(privateKey, digestAlgo, provider);
        byte[] attrSign = signature.sign(attributes);
        pkcs7.setExternalDigest(attrSign, null, signature.getEncryptionAlgorithm());
        byte[] cmsSignature = pkcs7.getEncodedPKCS7(digest);
    
        ReadySignatureContainer extSigContainer = new ReadySignatureContainer(cmsSignature);
        PdfDocument docToSign = new PdfDocument(new PdfReader(new ByteArrayInputStream(toSignedBytes)));
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        PdfSigner.signDeferred(docToSign, sigFieldName, output, extSigContainer);
        byte[] toByteArray = output.toByteArray();
        docToSign.close();
        output.flush();
        output.close();
        return toByteArray;
    }
    
    public void setDigestAlgo(String digestAlgo) {
        this.digestAlgo = digestAlgo;
    }
}
